/*
 * @Author: wuqingchun
 * @Date: 2023-08-16 14:11:57
 * @Description: 
 * @LastEditors: wuqingchun
 * @LastEditTime: 2024-01-10 20:07:22
 */
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>

#include "ftpc_log.h"
#include "ftpc_util.h"
#include "ftpc_tcp.h"

#define FTPC_TCP_BACKLOG 5
#define FTPC_TCP_CONN_TIMEOUT 6 // TCP 建立连接超时时间,单位为秒

static int __tcp_Connect(int iSockFd, const struct sockaddr* pstAddr, unsigned int uiAddrLen, unsigned int uiSec)
{
    int iErr;
    fd_set stWriteFds;
    struct timeval stTVal;

    iErr = connect(iSockFd, pstAddr, uiAddrLen);
    if (iErr == 0)
    {
        // 连接建立成功
        return 0;
    }
    
    if (errno != EINPROGRESS)
    {
        // 连接失败
        return -1;
    }

    FD_ZERO(&stWriteFds);
    FD_SET(iSockFd, &stWriteFds);
    stTVal.tv_sec = uiSec;
    stTVal.tv_usec = 0;
    int iNum = select(iSockFd + 1, NULL, &stWriteFds, NULL, &stTVal);
    if (iNum <= 0)
    {
        // == 0 超时连接没有建立
        // -1  发生错误
        // 这两种情况都需要关闭连接
        return -1;
    }

    // 监测到 iSockFd 的写事件并不能说明连接建立成功
    if (FD_ISSET(iSockFd, &stWriteFds))
    {
        iErr = 0;
        unsigned int uiLen = sizeof(iErr);
        if (getsockopt(iSockFd, SOL_SOCKET, SO_ERROR, &iErr, &uiLen) == -1)
        {
            // 建立连接失败
            return -1;
        }
        
        if (iErr != 0)
        {
            // 建立连接失败
            return -1;
        }
    }

    // 建立连接成功
    return 0;
}

int FTPC_tcp_Connect(const char *pcHostName, unsigned short usPort)
{
    int iErr;
    int iSockFd;
    struct sockaddr_in stAddr;

    bzero(&stAddr, sizeof(struct sockaddr_in));
    stAddr.sin_family = AF_INET;
    stAddr.sin_port = htons(usPort);
    iErr = inet_pton(AF_INET, pcHostName, &stAddr.sin_addr);
    if (iErr <= 0) 
    {
        return -1;
    }

    iSockFd = socket(AF_INET, SOCK_STREAM, 0);
    if (iSockFd == -1)
    {
        return -1;
    }

    iErr = FTPC_util_SetNoBlock(iSockFd);
    if (iErr == -1) 
    {
        return -1;
    }
    
    //struct timeval stTVal;
    //stTVal.tv_sec = 6;
    //stTVal.tv_usec = 0;

    //setsockopt(iSockFd, SOL_SOCKET, SO_SNDTIMEO, &stTVal, sizeof(stTVal));
    iErr = __tcp_Connect(iSockFd, (const struct sockaddr *)&stAddr, sizeof(struct sockaddr_in), FTPC_TCP_CONN_TIMEOUT);
    if (iErr == -1)
    {
        close(iSockFd);
        return -1;
    }

    return iSockFd;
}
/*
// 由于TCP为基于字节流的协议，同时 socket 发送缓冲区存在一定的大小限制
// 会出现发送缓冲区满导致的发送失败
// 发送结果存在如下三种情况
// 1. 全部发送成功
// 2. 部分发送成功，部分发送失败
// 3. 全部发送失败
// 对于2、3两种情形，发送失败的那部分数据，为避免数据丢失，需要外层考虑视具体情况进行重发
*/
ssize_t FTPC_tcp_Send(int iSockFd, void* pData, size_t ulDataLen)
{
    ssize_t lSendRet = 0;


    do
    {        
        lSendRet = send(iSockFd, pData, ulDataLen, 0);
        if (lSendRet == -1)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                // 本端socket发送缓冲区已满
                lSendRet = 0;
            }
        }
    } while (errno == EINTR); // 系统调用被信号中断，需要重试
    
    return lSendRet;
}

ssize_t FTPC_tcp_RecvNoblock(int iSockFd, void* pBuf, size_t ulBufSize)
{
    ssize_t lRecvSize;

    do
    {
        lRecvSize = recv(iSockFd, pBuf, ulBufSize, 0);
        if (lRecvSize == -1)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {               
                // The socket is marked nonblocking and the receive operation would block, or a receive timeout 
                // had been set and the  timeout  expired before  data  was received.
                lRecvSize = 0;
            }

            // 其他错误码说明对端连接已经关闭
        }
        else if (lRecvSize == 0)
        {
            // 对端连接已经关闭
            lRecvSize = -1;
        }
    } while (errno == EINTR); // 系统调用被信号中断，需要重试

    return lRecvSize;
}

// 非阻塞方式读数据
static ssize_t _tcp_Recv(int iSockFd, void* pBuf, size_t ulBufSize)
{
    ssize_t lRecvSize;

    do
    {
        lRecvSize = recv(iSockFd, pBuf, ulBufSize, MSG_DONTWAIT);
        //printf("%ld, sockfd=%d, errno=%d, %s, %s:%d\n", lRecvSize, iSockFd, errno, strerror(errno), __FILE__, __LINE__);
        if (lRecvSize == -1)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {               
                // The socket is marked nonblocking and the receive operation would block, or a receive timeout 
                // had been set and the  timeout  expired before  data  was received.
                lRecvSize = 0;
            }
        }
        else if (lRecvSize == 0)
        {
            // 对端连接已经关闭
            lRecvSize = -1;
        }
    } while (errno == EINTR); // 系统调用被信号中断，需要重试，其他错误码说明对端连接已经关闭

    return lRecvSize;
}

// 非阻塞方式读数据
int FTPC_tcp_Recv(int iSockFd, FTPC_BUF_HEAD_S* pstBufHead)
{
    int iErr = -1;
    size_t ulTotalLen = 0;
    ssize_t lRecvLen;
    FTPC_BUF_S* pstBuf;
    size_t ulBufSize;
    
    // 阻塞模式从网络上接收数据存在的问题
    // 1、读一次就结束，还是读多次
    //    如果读一次就结束，能确保数据都读完了吗？（需要上层解析之后才知道，通过长度或者结束符进行判定，难道读一次就解析一次，那为什么不做成异步非阻塞）
    //    如果读多次，前一次读完了，由于发送端不再发送新的数据，后一次读将永远阻塞(加入超时时间可以解决这个问题)

    // 非阻塞不一定是异步，也可以是同步，通过轮询的方式实现，大部分的时间都浪费在系统调用导致的用户态和内核上下文切换上

    // 所以异步+非阻塞结合事件驱动是一个比较好的选择，可以大大减少系统调用的频次
    // 每次处理异步事件读取数据时，需要保证将数据全部读走，否则如果后续不再有事件，则会出现剩余数据无法读走的问题

    // 阻塞 同步

    iErr = 0;
    do
    {
        pstBuf = FTPC_buf_Get(pstBufHead);
        if (pstBuf == NULL)
        {
            iErr = -1;
            break;
        }

        ulBufSize = FTPC_buf_GetSize(pstBufHead);
        lRecvLen = _tcp_Recv(iSockFd, pstBuf->pcBuf, ulBufSize);
        if (lRecvLen == -1)
        {
            iErr = -1;
            break;
        }

        pstBuf->ulDataSize += lRecvLen;
        ulTotalLen += lRecvLen;
    } while (lRecvLen == ulBufSize); // 数据还没有接收完成，需要继续

    //printf("ulTotalLen: %lu, %s:%d\n", ulTotalLen, __FILE__, __LINE__);
    if (ulTotalLen > 0)
    {
        return 0;
    }
    
    // 没有收到数据，连接需要关闭
    return -1;
}

int FTPC_tcp_Listen(struct sockaddr_in* pstAddr)
{
    int iErr;
    int iSockFd;
    socklen_t uiAddrLen;
    
    iSockFd = socket(AF_INET, SOCK_STREAM, 0);
    if (iSockFd == -1)
    {
        return -1;
    }

    uiAddrLen = sizeof(struct sockaddr_in);
    iErr = bind(iSockFd, (struct sockaddr* )pstAddr, uiAddrLen);
    if (iErr == -1)
    {
        FTPC_LOG_ERROR("Failed to bind address, %s", strerror(errno));
        close(iSockFd);
        return -1;
    }

    iErr = listen(iSockFd, FTPC_TCP_BACKLOG);
    if (iErr == -1)
    {
        FTPC_LOG_ERROR("Listen error, %s", strerror(errno));
        close(iSockFd);
        return -1;
    }

    iErr = FTPC_util_SetNoBlock(iSockFd);
    if (iErr == -1) 
    {
        close(iSockFd);
        FTPC_LOG_ERROR("FTPC_util_SetNoBlock error, %s", strerror(errno));
        return -1;
    }
    
    if (pstAddr->sin_port == 0)
    {
        // 若port指定为0,则调用bind时，系统会为其指定一个可用的端口号
        bzero(pstAddr, sizeof(struct sockaddr_in));
        uiAddrLen = sizeof(struct sockaddr_in);

        // 获取本地地址和端口
        iErr = getsockname(iSockFd, (struct sockaddr *)pstAddr, &uiAddrLen);
        if (iErr == -1)
        {
            FTPC_LOG_ERROR("Failed to get socket name, %s", strerror(errno));
            close(iSockFd);
            return -1;
        }
    }

    return iSockFd;
}
